我相信我們都很熟悉MySQL的索引規則了,因為MySQL的儲存引擎是用B+樹的資料結構儲存索引,所以會有一個最左匹配的規則。
為了讓查詢能夠吻合索引,索引使用欄位的順序必須由左至右,舉例來說:
SELECT * FROM table WHERE a = 1 AND b > 2 ORDER BY c;
這查詢最有效率的索引是一個複合索引,例如(a, b, c)
。但是,這樣的索引無法完全套用在下面這個查詢。
SELECT * FROM table WHERE a = 1 ORDER BY c;
這是因為,查詢少了b
這個欄位,因此這個複合索引只有第一個a
欄位會被使用。
這是MySQL的索引規則,大體來說,關聯式資料庫都遵守這樣的規則。但是,MongoDB的實作有點不同,儘管MongoDB的索引是以B樹儲存。
我們繼續沿用剛剛的查詢作為例子,以下這個查詢與剛剛MySQL的查詢是等價的,只是用MongoDB的MQL改寫了。
db.table.find({a: 1, b: {$gt: 2}}).sort({c: 1})
但是剛剛的索引(a, b, c)
對於這個查詢是無效的,取而代之的是,必須要用(a, c, b)
才能提升效能。
這是因為MongoDB的索引遵循ESR規則,也就是Equality-Sort-Range。因此,剛剛的查詢,b
欄位是範圍查詢R,而c
欄位是排序S。
所以正確的索引順序是(a, c, b)
。
與一般的關聯式資料庫相同,MongoDB亦支援索引交集。
索引交集指的是單一一個查詢,可以套用複數個索引,會使用這些索引的交集條件。
繼續以上面的查詢作為例子。
db.table.find({a: 1, b: {$gt: 2}}).sort({c: 1})
為了提升效能,我們剛提到要使用(a, c, b)
這個索引,但事實上,也可以透過兩個單獨索引來提升效能:
透過這兩個索引的交集,一樣可以提升查詢的效能,值得注意的是,ESR規則依然有效。
雖然官方文件說有支援索引交集,但我並不推薦大家主動使用,因為MongoDB的索引選擇器不一定足夠聰明去選擇兩個索引,另一方面,即便可以交集,但依然會需要CPU的運算,會比單一索引更消耗資源。
那索引交集的優點是什麼?
最大的優點是我們可以讓索引更彈性。如果只有一個索引(a, c, b)
,那麼在單獨查詢b
時就無法套用索引,因此得要再額外建立一個b
欄位的索引。
越多索引就會佔用越多記憶體,同時也會影響資料寫入的效能,所以索引交集可以讓索引的符合條件比較緊密。
但如我剛說的,不要把索引交集當成一個目的,而是當成一種背後的補救機制,當索引漏掉時「有機會」因為索引交集得到補救,但這不是一個可靠的機制。
當使用MySQL時,我們會用IN
做範圍的查詢,在MySQL中IN
是一種相等比對,也就是說,當查詢是WHERE a IN (2, 3)
等價於WHERE a = 2 OR a = 3
。
但是,MongoDB不這麼認為。
如果在查詢時單獨使用$in
,那麼就可以當作與MySQL相同行為,屬於ESR中的E。
舉例來說:find({a: {$in: [2, 3]}})
和find({$or: [{a: 2}, {a: 3}]})
是等價的,無論是前者還後者,都屬於E。
但是,如果和排序一起使用,那麼$in
就要視為範圍比對,也就是R。
find({a: {$in: [2, 3]}}).sort({b: 1})
這裡的a
會被視為R,所以為了吻合對應的查詢,索引要使用(b, a)
而不是(a, b)
。
如果只有使用關聯式資料庫的經驗,那在使用功能更豐富的NoSQL資料庫很容易踩到陷阱。尤其是MongoDB的底層是B樹其實與關聯式資料庫差不多,但是在實作上還是有許多差別。
當在建立MongoDB的索引時,要特別注意ESR規則。許多從MySQL轉移過來的MongoDB使用者非常容易在這個規則上栽跟斗。
事實上,即便是索引(a, b, c)
,在資料量體小的時候還是可以運作,MongoDB會將資料拉出來後在記憶體中進行排序。一但資料量大到無法裝進記憶體,而必須得要用到硬碟存取,那麼效能非常悲劇。